
/*    This file is part of the Managarm operating system.
 *   Copyright (C) 2007, 2008, 2009  Alexander van der Grinten
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

package org.managarm.jmanacclib.frontend.korona;

import ast.AstRoot;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;

import org.managarm.jmanacclib.core.CclibException;
import org.managarm.jmanacclib.core.CclibReader;
import org.managarm.jmanacclib.core.CclibSource;
import org.managarm.jmanacclib.core.CclibUnit;

public class KorEnvironment {
	public static class PrepSubstitution {
		public final String search;
		public final String replace;
		public PrepSubstitution(String search, String replace) {
			this.search = search;
			this.replace = replace;
		}
	}
	
	public static class PrepMacro {
		public final CclibSource source;
		public final String identifier;
		public final int arguments;
		public final String code;
		public final boolean extended;
		public PrepMacro(CclibSource source,
				String identifier, int arguments, String code,
				boolean extended) {
			this.source = source;
			this.identifier = identifier;
			this.arguments = arguments;
			this.code = code;
			this.extended = extended;
		}
	}
	
	public String workingDirectory;
	
	// list of directory pathes that are part of the current include path
	public final LinkedList<String> includePath;
	
	// list of files that were included
	public final LinkedList<String> includeFiles;
	
	// maps library names to library paths
	public final HashMap<String, String> libraries;
	
	// preprocessor macros and substitutions
	public final HashSet<String> prepDefinitions;
	public final KorIdentTree prepIdentifiers;
		
	public String standardEncoding = "UTF-8";
	
	public KorEnvironment() {
		includePath = new LinkedList<String>();
		includeFiles = new LinkedList<String>();
		libraries = new HashMap<String, String>();
		prepDefinitions = new HashSet<String>();
		prepIdentifiers = new KorIdentTree();
	}
	
	public void prepDefine(String ident) {
		prepDefinitions.add(ident);
	}
	
	public boolean prepIsDef(String ident) {
		return prepDefinitions.contains(ident);
	}
	
	public String findFile(final CclibUnit current,
			final String filename) throws CclibException {
		try {
			// check whether the file is inside the current directory
			final File targetfile = new File(filename);
			if(targetfile.exists()) {
				return targetfile.getCanonicalPath();
			}else if(targetfile.isAbsolute() == false && current != null) {
				final File parentfile = new File(current.unitPath);
				final File childfile = new File(parentfile.getParent()
						+ File.separator + filename);
				if(childfile.exists())
					return childfile.getCanonicalPath();
			}else if(targetfile.isAbsolute() == false
					&& workingDirectory != null) {
				final File childfile = new File(workingDirectory
						+ File.separator + filename);
				if(childfile.exists())
					return childfile.getCanonicalPath();
			}
			
			// check whether the file is inside the include path
			final Iterator<String> iterator = includePath.iterator();
			while(iterator.hasNext()) {
				final String directory = iterator.next();
				final File childfile = new File(directory
						+ File.separator + filename);
				if(childfile.exists())
					return childfile.getCanonicalPath();
			}
		} catch(IOException e) {
			throw new CclibException("Could not process source file: "
					+ filename);
		}
		
		throw new CclibException("Could not find source file: "
				+ filename, current, -1);
	}
	
	public CclibUnit findUnit(final CclibUnit current, int line,
			final String filename) throws CclibException {
		final File file = new File(findFile(current, filename));
		try {
			return new CclibUnit(current, line, file.getCanonicalPath());
		} catch(IOException e) {
			throw new CclibException("Could not process source file "
					+ filename);
		}
	}
	
	public CclibReader openFile(final CclibUnit unit)
			throws CclibException {
		final CclibSource source = new CclibSource(unit);
		
		try {
			includeFiles.add(unit.unitPath);
			
			// open the source file for reading
			final FileInputStream stream = new FileInputStream(unit.unitPath);
			final InputStreamReader input
				= new InputStreamReader(stream, standardEncoding);
			
			// create a reader object to read from the file
			return new CclibReader.FileReader(source, input);
		} catch(IOException e) {
			throw new CclibException("Could not open source file "
					+ unit.unitPath);
		}
	}
	
	public void parseFile(final KorAstNode root,
			final CclibUnit unit) throws CclibException {
		final CclibReader reader = openFile(unit);
		final KorParser parser = new KorParser(reader, this);
		parser.parse((AstRoot)root); // parse the source file
	}
}
